//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using EffiProzDB.Lib;


namespace EffiProzDB
{



    // fredt@users 20020420 - patch523880 by leptipre@users - VIEW support - modified
    // fredt@users 20031227 - remimplementated as compiled query

    /**
     * Represents an SQL VIEW based on a SELECT statement.
     *
     * @author leptipre@users
     * @author fredt@users
     * @version 1.8.0
     * @since 1.7.0
     */
    public class View : Table
    {

        public Select viewSelect;
        public SubQuery viewSubQuery;
        private string statement;
        private HsqlNameManager.HsqlName[] colList;

        ///** schema at the time of compilation */
        public HsqlNameManager.HsqlName compileTimeSchema;

        /**
         * List of subqueries _in this view _in order of materialization. Last
         * element is the view itself.
         */
        public SubQuery[] viewSubqueries;

        /**
         * Constructor.
         * @param Session
         * @param db database
         * @param name HsqlNameManager.HsqlName of the view
         * @param definition SELECT statement of the view
         * @param columns array of HsqlNameManager.HsqlName column names
         * @ 
         */

        public View(Session session, Database db, HsqlNameManager.HsqlName name, string definition,
                HsqlNameManager.HsqlName[] columns) :base(db, name, VIEW)
        {           

            _isReadOnly = true;
            colList = columns;
            statement = trimStatement(definition);
            compileTimeSchema = session.getSchemaHsqlName(null);

            compile(session);
            replaceAsterisksInStatement();

            HsqlNameManager.HsqlName[] schemas = getSchemas();

            for (int i = 0; i < schemas.Length; i++)
            {
                if (db.schemaManager.isSystemSchema(schemas[i]))
                {
                    continue;
                }

                if (!schemas[i].Equals(name.schema))
                {
                    throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
                }
            }
        }

        /**
         * Returns the SELECT statement trimmed of any terminating SQL
         * whitespace, separators or SQL comments.
         */
        public static string trimStatement(string s)
        {

            int position;
            string str;
            Tokenizer tokenizer = new Tokenizer(s);

            // fredt@users - this establishes the end of the actual statement
            // to get rid of any end semicolon or comment line after the end
            // of statement
            do
            {
                position = tokenizer.getPosition();
                str = tokenizer.getString();
            } while (str.Length != 0 || tokenizer.wasValue());

            return s.Substring(0, position - (0)).Trim();
        }

        /**
         * Compiles the SELECT statement and sets up the columns.
         */
        public void compile(Session session)   {

            // create the working table
            Parser p = new Parser(session, this.database,
                                  new Tokenizer(statement));

            p.setCompilingView();

            int brackets = p.parseOpenBracketsSelect();

            viewSubQuery = p.parseSubquery(brackets, colList, true,
                                           Expression.VIEW);

            p.setAsView(this);

            viewSubqueries = p.getSortedSubqueries();
            viewSelect = viewSubQuery.select;

            viewSelect.prepareResult(session);

            Result.ResultMetaData metadata = viewSelect.resultMetaData;
            int columns = viewSelect.iResultLen;

            if (base.columnCount == 0)
            {

                // do not add columns at recompile time
                base.addColumns(metadata, columns);
            }

        }

        /**
         * Returns the SELECT statement for the view.
         */
        public string getStatement()
        {
            return statement;
        }

        /**
         *  is a private helper for replaceAsterisksInStatement, to avoid some code duplication
         */
        private void collectAsteriskPos( Select select,
                                        Dictionary<int,object> asteriskPositions) {

            if (select.asteriskPositions == null) {
                return;
            }

          
            foreach (var pos in select.asteriskPositions.Keys)
            {         

                asteriskPositions.Add(pos, select.asteriskPositions[pos]);
            }

            // The following is to guarantee the invariant of this class, that the |astersiskPositions|
            // member of the various Select instances properly describe the occurances
            // of Expression.ASTERISK _in the statement.
            // Since we are going to replace all those asterisks, we also need to reset the various
            // |astersiskPositions| instances, which is best done here were all non-null
            // Select's need to pass by.
            select.asteriskPositions = null;
        }

        /**
         *  replaces all asterisks _in our statement with the actual column list
         *
         *  This way, we ensure what is required by the standard: a view returns a result
         *  which reflects the structure of the underlying tables at the *time of the definition
         *  of the view.
         */
        private void replaceAsterisksInStatement()   {

            Dictionary<int, object> asteriskPositions = new Dictionary<int, object>();

            // asterisk positions _in sub queries
            for (int i = 0; i < viewSubqueries.Length; ++i) {

                // collect the occurances of asterisks _in the statement
                Select subSelect = viewSubqueries[i].select;

                collectAsteriskPos(subSelect, asteriskPositions);

                // the same for all (possible) UNION SELECTs of the sub select
                if (subSelect.unionArray != null) {

                    // start with index 1, not 0 - the first select is the one already covered by subSelect
                    for (int u = 1; u < subSelect.unionArray.Length; ++u) {
                        collectAsteriskPos(subSelect.unionArray[u],
                                           asteriskPositions);
                    }
                }
            }

            int[]    positions = new int[asteriskPositions.Count];
         
            int ii = 0;
            foreach (var pos in asteriskPositions.Keys)
            {
                positions[ii++] = pos;
            }

            ArrayUtil.sortArray(positions);

            StringBuilder expandedStatement = new StringBuilder();
            int          lastPos           = 0;
            string       segment;

            for (int i = 0; i < positions.Length; i++) {
                int    pos     = positions[i];
                string colList = (String) asteriskPositions[pos];

                if (colList == null) {
                    continue;
                }

                segment = statement.Substring( lastPos ,  pos - (lastPos) );
                lastPos = statement.IndexOf("*", pos) + 1;

                expandedStatement.Append(segment);
                expandedStatement.Append(' ');
                expandedStatement.Append(colList);
                expandedStatement.Append(' ');
            }

            segment = statement.Substring( lastPos ,  statement.Length  - (lastPos) );

            expandedStatement.Append(segment);

            statement = expandedStatement.ToString();
        }

        /**
         * Overridden to disable SET TABLE READONLY DDL for View objects.
         */
        public override void setDataReadOnly(bool value)
        {
            throw Trace.error(Trace.NOT_A_TABLE);
        }

        /**
         * Returns list of schemas
         */
        HsqlNameManager.HsqlName[] getSchemas() {

            List<HsqlNameManager.HsqlName> list = new List<HsqlNameManager.HsqlName>();

            for (int i = 0; i < viewSubqueries.Length; i++) {
                Select select = viewSubqueries[i].select;

                for (; select != null; select = select.unionSelect) {
                    TableFilter[] tfilter = select.tFilter;

                    for (int j = 0; j < tfilter.Length; j++) {
                        list.Add(tfilter[j].filterTable.tableName.schema);
                    }
                }
            }

            return  list.ToArray();
        }

        public bool hasView(View view) {

            if (view == this)
            {
                return false;
            }

            for (int i = 0; i < viewSubqueries.Length; i++)
            {
                if (viewSubqueries[i].view == view)
                {
                    return true;
                }
            }

            return false;
        }

        /**
         * Returns true if the view references any column of the named table.
         */
       public bool hasTable(Table table) {

           for (int i = 0; i < viewSubqueries.Length; i++)
           {
               Select select = viewSubqueries[i].select;

               for (; select != null; select = select.unionSelect)
               {
                   TableFilter[] tfilter = select.tFilter;

                   for (int j = 0; j < tfilter.Length; j++)
                   {
                       if (table.Equals(tfilter[j].filterTable.tableName))
                       {
                           return true;
                       }
                   }
               }
           }

            return false;
        }

        /**
         * Returns true if the view references the named column of the named table,
         * otherwise false.
         */
       public bool hasColumn(Table table, string colname) {

           if (hasTable(table))
           {
               Expression.Collector coll = new Expression.Collector();

               coll.addAll(viewSubqueries[viewSubqueries.Length - 1].select,
                           Expression.COLUMN);

         
               foreach (var e in coll )
               {           

                   if (colname.Equals(e.getBaseColumnName())
                           && table.Equals(e.getTableHsqlName()))
                   {
                       return true;
                   }
               }
           }

            return false;
        }

        /**
         * Returns true if the view references the named SEQUENCE,
         * otherwise false.
         */
        public bool hasSequence(NumberSequence sequence) {

            Expression.Collector coll = new Expression.Collector();

            coll.addAll(viewSubqueries[viewSubqueries.Length - 1].select,
                        Expression.SEQUENCE);         

            foreach (var e in coll)
            {   

                if (e.valueData == sequence)
                {
                    return true;
                }
            }

            return false;
        }
    }
}